package cn.liuxiany.alibaba.consumer.config;


import cn.liuxiany.alibaba.common.request.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;

import javax.validation.ConstraintViolation;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    /**
     * 异常localizedMessage
     */
    private static String localizedMessage = "localizedMessage";

    /**
     * 异常栈信息
     */
    private static String stackTrace = "stackTrace";


    /**
     * 捕获404异常
     * 配置文件中必须配置：
     * spring:
     * mvc:
     * throw-exception-if-no-handler-found: true
     * web:
     * resources:
     * #关闭资源映射
     * add-mappings: false
     * <p>
     * 返回 HttpStatus.OK 网关才会显示R.failed的数据。
     *
     * @param ex
     * @param headers
     * @param status
     * @param request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("handleNoHandlerFoundException is {}", stackMap);

        Result<Object> baseResponse = Result.failed("请求路径错误！");

        return new ResponseEntity<>(baseResponse, headers, HttpStatus.OK);
    }

    /**
     * assert相关异常处理
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<Object> assertException(IllegalArgumentException ex) {

        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("assert Exception is {}", stackMap);

        return Result.failed(ex.getMessage());
    }

    /**
     * 非bean参数验证异常 requestParam参数
     */
    @ExceptionHandler(javax.validation.ConstraintViolationException.class)
    public Object validatedConstraintViolationExceptionException(javax.validation.ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        StringBuffer stringBuffer = new StringBuffer();
        Iterator<ConstraintViolation<?>> iterator = constraintViolations.stream().iterator();
        while (iterator.hasNext()) {
            ConstraintViolation<?> next = iterator.next();
            String message = next.getMessage();

            stringBuffer.append(message).append("|");
        }

        String message = stringBuffer.toString();
        if (message.endsWith("|")) {
            message = message.substring(0, message.length() - 1);
        }

        return Result.failed(message);
    }

    /**
     * Bean Validator
     * bean验证异常 requestBody
     * <p>
     * 参数校验，使用SpringBoot推荐的Hibernate Validator
     *
     * @see javax.validation.Valid
     * @see org.springframework.validation.annotation.Validated
     */
    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
                                                               HttpStatus status, WebRequest request) {
        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("MethodArgumentNotValidException is {}", stackMap);

        BindingResult result = ex.getBindingResult();
        StringBuilder sbu = new StringBuilder();
        for (ObjectError error : result.getAllErrors()) {
            sbu.append(error.getDefaultMessage() + "|");
        }
        String str = sbu.toString();
        if (str.endsWith("|")) {
            str = str.substring(0, str.length() - 1);
        }

        Result<Object> baseResponse = Result.failed(str);
        return handleExceptionInternal(ex, baseResponse, headers, status, request);
    }

    /**
     * 处理数据绑定异常.<br>
     */
    @Override
    public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
                                                      WebRequest request) {
        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("BindException is {}", stackMap);

        BindingResult result = ex.getBindingResult();
        StringBuilder sbu = new StringBuilder();
        for (ObjectError error : result.getAllErrors()) {
            sbu.append(error.getDefaultMessage() + "|");
        }

        String str = sbu.toString();
        if (str.endsWith("|")) {
            str = str.substring(0, str.length() - 1);
        }

        Result<Object> baseResponse = Result.failed(str);

        return handleExceptionInternal(ex, baseResponse, headers, status, request);
    }

    /**
     * 处理数据转换异常. <br>
     */
    @Override
    public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers,
                                                               HttpStatus status, WebRequest request) {
        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("HttpMessageNotReadableException is {}", stackMap);
        Result<Object> baseResponse = Result.failed("数据转换异常，请检查传参！");

        return new ResponseEntity<>(baseResponse, headers, HttpStatus.OK);
    }

    /**
     * 5xx异常. <br>
     * 处理其他异常 * 虚拟机默认使用jvm, message和StackTrace都有值<br>
     */
    @ExceptionHandler(Exception.class)
    public Result<Object> exception(Exception ex) {
        Map<String, String> stackMap = getExceptionMsg(ex);
        log.error("Exception is {}", stackMap);

        return Result.failed("出现未知错误，请稍后再试！");
    }

    /**
     * 重写springframework响应实体类.<br>
     *
     * @see ResponseEntity
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers,
                                                             HttpStatus status, WebRequest request) {

        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        return new ResponseEntity<>(body, headers, HttpStatus.OK);
    }

    /**
     * 获取异常栈信息
     *
     * @param e Exception
     * @return 返回localizedMessage、stackTrace
     */
    private static Map<String, String> getExceptionMsg(Exception e) {
        StringBuffer stackTraceSb = new StringBuffer();
        String s = e.getClass().getName();
        String localizedMsg = e.getLocalizedMessage();
        stackTraceSb.append((localizedMsg != null) ? (s + ": " + localizedMsg + "\n") : s + "\n");
        StackTraceElement[] st = e.getStackTrace();
        int line = 0;
        for (StackTraceElement traceElement : st) {
            stackTraceSb.append("\tat ").append(traceElement.toString()).append("\n");
            if (line++ >= 30) {
                stackTraceSb.append("... ...\n");
                break;
            }
        }
        Map<String, String> stackMap = new HashMap<>();
        stackMap.put(localizedMessage, localizedMsg);
        stackMap.put(stackTrace, stackTraceSb.toString());

        return stackMap;
    }

}
